package org.jeecg.modules.zhongche.plc.runner;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.PlcConnectionManager;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.jeecg.modules.zhongche.plc.api.PlcFoot;
import org.jeecg.modules.zhongche.plc.config.PLCProperties;
import org.jeecg.modules.zhongche.plc.model.PLCReadDTO;
import org.jeecg.modules.zhongche.plc.model.PlcReceiveDataBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
@Getter
public class PLCRunner implements ApplicationRunner {

    private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(16);
    public Map<Integer, PlcFoot> dataBlockInfoMap = new HashMap<>();

    @Autowired
    private PLCProperties plcProperties;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;


    @Override
    public void run(ApplicationArguments args) {
        if (ObjUtil.isEmpty(plcProperties.getDevices())) {
            log.error("plc设备为空");
            return;
        }

        for (PLCProperties.Device device : plcProperties.getDevices()) {
            if (ObjUtil.isEmpty(device.getDataBlocks())) {
                continue;
            }

            String connectionString = device.getIp();
            PlcConnectionManager plcConnectionManager = PlcDriverManager.getDefault().getConnectionManager();

            if (null == plcConnectionManager) {
                continue;
            }
            PlcConnection plcConnection = null;
            try {
                plcConnection = plcConnectionManager.getConnection(connectionString);
                if (!plcConnection.getMetadata().isReadSupported()) {
                    log.error("This connection doesn't support reading.");
                    return;
                }
            } catch (Exception e) {
                log.error("PLC链接失败{}", e.getMessage());
            }
            if (null == plcConnection) {
                continue;
            }

            List<PlcFoot> footList = new ArrayList<>();
            for (PLCProperties.DataBlock dataBlock : device.getDataBlocks()) {
                PlcReadRequest.Builder read = plcConnection.readRequestBuilder();
                PlcFoot foot = new PlcFoot(dataBlock.getDatabaseNumber(), plcConnection, read);
                foot.register(PLCReadDTO.class);
                dataBlockInfoMap.put(dataBlock.getDatabaseNumber(), foot);
                footList.add(foot);
            }

            if (device.getReadRepeat()) {
                withDataBlock(footList);
            }
        }

    }

    @SneakyThrows
    protected void withDataBlock(List<PlcFoot> footList) {
        //开启轮询读取数据
        executorService.scheduleWithFixedDelay(() -> {
            try {
                List<PlcReceiveDataBO> list = footList.stream()
                        .map(foot -> foot.readClass(PLCReadDTO.class)
                                .thenApply(val -> {
                                    log.info("plc读取到的数据 = {}", val);
                                    PlcReceiveDataBO plcReceiveDataBO = new PlcReceiveDataBO();
                                    BeanUtil.copyProperties(val.getData(), plcReceiveDataBO);
                                    return mainData(plcReceiveDataBO, foot.getDbNum());
                                })
                                .exceptionally((e) -> {
                                    log.error("plc读取数据错误={}", e.getMessage());
                                    PlcReceiveDataBO plcReceiveDataBO = new PlcReceiveDataBO();
                                    return mainData(plcReceiveDataBO, foot.getDbNum());
                                }))
                        .map(CompletableFuture::join)
                        .toList();

            } catch (Exception e) {
                log.error("publishEvent错误={}", e.getMessage());
            }
        }, 1000, 5000, TimeUnit.MILLISECONDS);


//        executorService.scheduleWithFixedDelay(() -> {
//
//            List<CompletableFuture<PlcReceiveDataBO>> futureList = new ArrayList<>();
//
//            for (PlcFoot foot : footList) {
//                CompletableFuture<PlcReceiveDataBO> future = foot.execute(PLCReadDTO.class).thenApply(val -> {
//                    log.info("读取到的数据 = {}", val);
//                    // 发送消息
//                    PlcReceiveDataBO plcReceiveDataBO = new PlcReceiveDataBO();
//                    BeanUtil.copyProperties(val.getData(), plcReceiveDataBO);
//                    plcReceiveDataBO.setDate(LocalDateTime.now());
//                    plcReceiveDataBO.setDbNo(val.getDbNo());
//                    plcReceiveDataBO.setDeviceName(StrUtil.format("设备{}", foot.getDbNum()));
//                    plcReceiveDataBO.setAlias(StrUtil.format("{}号机器人", foot.getDbNum()));
//                    return plcReceiveDataBO;
//                }).exceptionally((e) -> {
//                    log.error("读取错误={}", e.getMessage());
//                    PlcReceiveDataBO plcReceiveDataBO = new PlcReceiveDataBO();
//                    plcReceiveDataBO.setDbNo(foot.getDbNum());
//                    plcReceiveDataBO.setDate(LocalDateTime.now());
//                    plcReceiveDataBO.setDeviceName(StrUtil.format("device{}", foot.getDbNum()));
//                    plcReceiveDataBO.setAlias(StrUtil.format("{}号机器人", foot.getDbNum()));
//                    return plcReceiveDataBO;
//                });
//                futureList.add(future);
//            }
//            try {
//                CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();
//                List<PlcReceiveDataBO> list = futureList.stream().map(CompletableFuture::join).toList();
//                applicationEventPublisher.publishEvent(new PlcEvent(list));
//            } catch (Exception e) {
//                log.error("publishEvent错误={}", e.getMessage());
//            }
//
//        }, 1000, 5000, TimeUnit.MILLISECONDS);
    }

    public PlcReceiveDataBO mainData(PlcReceiveDataBO bo, int dbNum) {
        bo.setDbNo(dbNum);
        bo.setDate(LocalDateTime.now());
        bo.setDeviceName(StrUtil.format("device{}", dbNum));
        bo.setAlias(StrUtil.format("{}号机器人", dbNum));
        return bo;
    }

}
